package dbservice

import (
	"context"
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"strconv"
	"time"

	"src/zeus/common"
	"src/zeus/logger"

	"github.com/redis/go-redis/v9"
)

type SessionUtil struct {
	Pid      uint64
	RedisKey string
}

type sessionUtil struct {
	pid      uint64
	Rediskey string
}

// SessionInfo 字段表
type SessionInfo struct {
	Token     string `redis:"token"`
	EntityID  uint64 `redis:"entityID"`
	GatewayId uint64 `redis:"gatewayId"`
	Groupid   uint32 `redis:"groupid"`
}

// Session里用户的信息
type SessionUserInfo struct {
	UID     uint64 `redis:"uid"`
	UserAge int32  `redis:"userage"`
	IsAdult bool   `redis:"isadult"`
	OpenId  string `redis:"openId"`
}

const (
	// SessionPrefix redis key后缀
	sessionPrefix = "session"

	//玩家的ID
	SessionInfo_uidkey = "uid"
	SessionInfo_Token  = "token"
	SessionInfo_IP     = "ip" //验证的时候，把IP写进去
	// SessionInfo_EntityID Session表中存放entityID的字段
	SessionInfo_EntityID = "entityID"
	// SessionInfo_GatewayId 玩家登录GatewayId
	SessionInfo_GatewayId = "gatewayId"
	SessionInfo_groupid   = "groupid"

	//是否成年
	SessionInfo_isadult = "isadult"
	//年龄
	SessionInfo_userage = "userage"
	//玩家的外部OpenId
	SessionInfo_OpenIdKey = "openId"
)

const (
	_ = iota
	userStateLogouting
)

var TokenVerifyError = errors.New("Token Error")

// NewSessionUtil 获得Session表的工具类
func NewSessionUtil(pid uint64) *sessionUtil {
	sessUtil := &sessionUtil{}
	sessUtil.pid = pid
	sessUtil.Rediskey = fmt.Sprintf("%s:%d", sessionPrefix, pid)
	return sessUtil
}

// 获取Session上的所有数据
func (this *sessionUtil) GetSessInfo(rd redis.Cmdable) (map[string]string, error) {
	return rd.HGetAll(context.Background(), this.Rediskey).Result()
}

// 保存数据
func (this sessionUtil) SaveSessInfo(rd redis.Cmdable, v map[string]string) error {
	return rd.HMSet(context.Background(), this.Rediskey, v).Err()
}

// // SaveSessionInfo 保存session信息, 包括entityID等
// func (util *sessionUtil) SaveSessionInfo(info *SessionInfo) error {
// 	c := GetSessionRedis()
// 	defer c.Dispose()
// 	if err := c.HMSet(c.Ctx, util.Rediskey, info).Err(); err != nil {
// 		return err
// 	}
// 	c.Expire(c.Ctx, util.Rediskey, Expire_Time)
// 	return nil
// 	// if _, err := c.Do("HMSET", redis.Args{}.Add(util.key()).AddFlat(info)...); err != nil {
// 	// 	return err
// 	// }
// 	// c.Expire(util.rediskey, Expire_Time)
// 	// //logger.Error("SaveSessionInfo ")
// 	// return nil
// }

// 写Nttid如果失败，需要把用户T掉
func (this *sessionUtil) SetEntityID(token string, nttid uint64) bool {
	c := GetSessionRedis()
	defer c.Dispose()
	err := c.Watch(c.Ctx, func(tx *redis.Tx) error {
		rtoken, err := tx.HGet(c.Ctx, this.Rediskey, SessionInfo_Token).Result()
		if err != nil {
			return err
		}
		if token != rtoken {
			return TokenVerifyError
		}
		_, err = tx.TxPipelined(c.Ctx, func(p redis.Pipeliner) error {
			return p.HMSet(c.Ctx, this.Rediskey, SessionInfo_EntityID, nttid).Err()
		})
		return err
	}, this.Rediskey)
	if err != nil {
		logger.Warnf("sessionUtil.VerifyToken error:%s", err)
		return false
	}
	return true
}

// // 保存用户信息 批量写所有结构体的字段
// func (util *sessionUtil) SaveSessionUser(info *SessionUserInfo) error {
// 	c := GetSessionRedis()
// 	defer c.Dispose()
// 	if err := c.HMSet(c.Ctx, util.Rediskey, info).Err(); err != nil {
// 		return err
// 	}
// 	c.Expire(c.Ctx, util.Rediskey, Expire_Time)
// 	return nil
// 	// if _, err := c.Do("HMSET", redis.Args{}.Add(util.key()).AddFlat(info)...); err != nil {
// 	// 	return err
// 	// }
// 	// c.Expire(util.rediskey, Expire_Time)
// 	// //logger.Error("SaveSessionInfo ")
// 	// return nil
// }

// // SetToken 生成token并将token保存至redis
// // 返回token和err
// func (util *sessionUtil) SetToken() (string, error) {
// 	t, tt := util.CreateToken()
// 	c := GetSessionRedis()
// 	defer c.Dispose()
// 	if err := c.HMSet(c.Ctx, util.Rediskey, SessionInfo_Token, t, SessionInfo_TokenTime, tt).Err(); err != nil {
// 		logger.Error(err)
// 		return "", err
// 	}
// 	return t, nil

// 	// if _, err := c.Hmset(util.key(), "token", t, "tktime", tt); err != nil {
// 	// 	logger.Error(err)
// 	// 	return "", err
// 	// }
// 	// return t, nil
// }

// GetToken 返回token
// 返回token和err
func (util *sessionUtil) GetToken() (string, error) {
	c := GetSessionRedis()
	return c.HGet(c.Ctx, util.Rediskey, SessionInfo_Token).Result()
	// return redis.String(util.getValue("token"))
}

// VerifyToken 验证Token有效性
// 返回验证结果: true有效, false无效
func (this *sessionUtil) VerifyToken(token string, ip string) bool {
	c := GetSessionRedis()
	defer c.Dispose()
	err := c.Watch(c.Ctx, func(tx *redis.Tx) error {
		rtoken, err := tx.HGet(c.Ctx, this.Rediskey, SessionInfo_Token).Result()
		if err != nil {
			return err
		}
		if token != rtoken {
			return TokenVerifyError
		}
		_, err = tx.TxPipelined(c.Ctx, func(p redis.Pipeliner) error {
			return p.HMSet(c.Ctx, this.Rediskey, SessionInfo_IP, ip).Err()
		})
		return err
	}, this.Rediskey)
	if err != nil {
		logger.Warnf("sessionUtil.VerifyToken error:%s", err)
		return false
	}
	return true
	// ret, err := c.HMGet(c.Ctx, util.Rediskey, SessionInfo_Token).Result()
	// // ret, err := c.Hmget(util.key(), "token", "tktime")
	// if err != nil {
	// 	logger.Error(err)
	// 	return false
	// }
	// // if len(ret) != 2 {
	// // 	logger.Error(err)
	// // 	return false
	// // }
	// if ret[0].(string) != token {
	// 	logger.Debug(logger.LogKey_LoginFlow, "[sessionUtil] VerifyToken faild: ", token, ret)
	// 	return false
	// }
	// // if ret["token"] != token {
	// // 	logger.Debug(logger.LogKey_LoginFlow, "[sessionUtil] VerifyToken faild: ", token, ret)
	// // 	return false
	// // }
	// return true
}

// DelToken 删除Token
// 返回err
func (util *sessionUtil) DelToken() error {
	c := GetSessionRedis()
	defer c.Dispose()
	return c.HDel(c.Ctx, util.Rediskey, SessionInfo_Token).Err()
	// _, err := c.Do("HDEL", util.key(), "token")
	// if err != nil {
	// 	return err
	// }
	// //	logger.Error("DelToken ")
	// return nil
}

// DelSession 删除Session表
func (util *sessionUtil) DelSession(token string) error {
	// if !util.VerifyToken(token) {
	// 	return TokenVerifyError
	// }

	c := GetSessionRedis()
	defer c.Dispose()
	return c.Del(c.Ctx, util.Rediskey).Err()
	// _, err := c.Do("DEL", util.key())
	// //	logger.Error("DelSession ")
	// return err
}

// // IsExisted Session表是否存在
// func (util *sessionUtil) IsExisted() (bool, error) {
// 	c := GetSessionRedis()
// 	defer c.Dispose()

// 	reply, err := c.Do("EXISTS", util.key())
// 	if err != nil {
// 		return false, err
// 	}
// 	return redis.Bool(reply, nil)
// }

// SetUserEntityID 设置玩家的EntityID
// func (util *sessionUtil) SetUserEntityID(entityID uint64) error {
// 	return util.setValue(redisEntityID, entityID)
// }

// GetUserEntityID 获得玩家的EntityID
func (util *sessionUtil) GetUserEntityID() (uint64, error) {
	reply, err := util.getValue(SessionInfo_EntityID)
	if err != nil {
		return 0, err
	}
	return common.NewString(reply).ToUint64()
	// if reply == nil {
	// 	return 0, errors.New("not user login")
	// }
	// //logger.Error("GetUserEntityID ")
	// return redis.Uint64(reply, err)
}

// GetIP 获取玩家的登录IP
func (util *sessionUtil) GetIP() (string, error) {
	reply, err := util.getValue(SessionInfo_IP)
	if err != nil {
		return "", err
	}
	return reply, nil
	// return redis.String(reply, nil)
}

// GetGatewaySvrId 获取玩家上一次登录过的Gateway地址
func (util *sessionUtil) GetGatewaySvrId() (uint64, error) {
	reply, err := util.getValue(SessionInfo_GatewayId)
	if err != nil {
		return 0, err
	}
	return common.NewString(reply).ToUint64()
	// if reply == nil {
	// 	return 0, errors.New("user not login")
	// }
	// return redis.Uint64(reply, err)
}

// 获取玩家UserID
func (util *sessionUtil) GetUserID() (uint64, error) {
	reply, err := util.getValue(SessionInfo_uidkey)
	if err != nil {
		return 0, err
	}
	return common.NewString(reply).ToUint64()
	// return redis.Uint64(reply, nil)
}

// 获取玩家OpenId
func (util *sessionUtil) GetOpenID() (string, error) {
	reply, err := util.getValue(SessionInfo_OpenIdKey)
	if err != nil {
		return "", err
	}
	return reply, nil
	// return redis.String(reply, nil)
}

// 获取玩家UserAge
func (util *sessionUtil) GetUserAge() (uint64, error) {
	reply, err := util.getValue(SessionInfo_userage)
	if err != nil {
		return 0, err
	}
	return common.NewString(reply).ToUint64()
	// return redis.Uint64(reply, nil)
}
func (util *sessionUtil) SetUserAgeByGM(v int) {
	c := GetSessionRedis()
	defer c.Dispose()
	if err := c.HMSet(c.Ctx, util.key(), SessionInfo_userage, v).Err(); err != nil {
		logger.Error(err)
		return
	}
	return
}

// 获取玩家是否成年
func (util *sessionUtil) GetIsAdult() (bool, error) {
	reply, err := util.getValue(SessionInfo_isadult)
	if err != nil {
		return false, err
	}
	return common.NewString(reply).ToBool() //redis.Bool(reply, nil)
}

// 设置玩家是否成年
func (util *sessionUtil) SetIsAdult(v bool) error {
	c := GetSessionRedis()
	defer c.Dispose()
	if err := c.HMSet(c.Ctx, util.key(), SessionInfo_isadult, v).Err(); err != nil {
		logger.Error(err)
		return err
	}
	return nil
}

// 获取玩家 ageMonthPay
func (util *sessionUtil) GetAgeMonthPay(uid uint64, checkDate bool) (int, error) {
	return Account(uid).GetAgeMonthPay(checkDate)
}
func (util *sessionUtil) SetAgeMonthPay(uid uint64, v int) error {
	return Account(uid).SetAgeMonthPay(v)
}

func (util *sessionUtil) getValue(field string) (string, error) {
	c := GetSessionRedis()
	defer c.Dispose()
	return c.HGet(c.Ctx, util.key(), field).Result()
}

func (util *sessionUtil) setValue(field string, value interface{}) error {
	c := GetSessionRedis()
	defer c.Dispose()
	err := c.HSet(c.Ctx, util.key(), field, value).Err()
	return err
}

func (util *sessionUtil) Expire() {
	c := GetSessionRedis()
	defer c.Dispose()
	c.Expire(c.Ctx, util.Rediskey, Expire_Time)
	return
}

// func (util *sessionUtil) removeExpire(c *redislib.RedisHandleModel) error {
// 	if c == nil {
// 		c = GetSessionRedis()
// 		defer c.Dispose()
// 	}

// 	_, err := c.Do("PERSIST", util.key())
// 	//	logger.Error("removeExpire")
// 	return err
// }

// getSessionKey 获取session表的Key
func (util *sessionUtil) key() string {
	return util.Rediskey
}

// createToken 方法根据uid和当前时间生成唯一的token
// md5(curtime+uid)
func (util *sessionUtil) CreateToken() (string, int64) {
	curtime := time.Now().Unix()
	h := md5.New()

	io.WriteString(h, strconv.FormatInt(curtime, 10))
	io.WriteString(h, strconv.FormatUint(util.pid, 10))
	token := fmt.Sprintf("%x", h.Sum(nil))

	return token, curtime
}
